package com.guaoran.source.spring.framework.context;

import com.guaoran.source.spring.framework.annotation.Autowried;
import com.guaoran.source.spring.framework.annotation.Controller;
import com.guaoran.source.spring.framework.annotation.Service;
import com.guaoran.source.spring.framework.aop.AopConfig;
import com.guaoran.source.spring.framework.beans.BeanDefinition;
import com.guaoran.source.spring.framework.beans.BeanWrapper;
import com.guaoran.source.spring.framework.context.support.BeanDefinitionReader;
import com.guaoran.source.spring.framework.core.BeanFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author : 孤傲然
 * @Description :
 * @date :2018/6/19 18:46
 */
public class ApplicationContext extends DefaultListableBeanFactory implements BeanFactory {
    private String[] configLocations;
    private BeanDefinitionReader definitionReader;
    /**用来保证注册式单利*/
    private Map<String, Object> beanCacheMap = new HashMap<String, Object>();
    /**用来存储所有被代理的bean*/
    private Map<String, BeanWrapper> beanWrapperMap = new ConcurrentHashMap<String, BeanWrapper>();


    /**
     * spring容器启动
     *
     * @param configLocations
     */
    public ApplicationContext(String... configLocations) {
        this.configLocations = configLocations;
        //进行初始化spring容器
        this.refresh();
    }

    /**
     * 初始化最核心的方法，把所有的bean重新构造一遍
     * 在创建IOC容器前，如果已经有容器存在，则需要把已有的容器进行销毁和关闭，以保证在refresh()后使用的是新建立起来的IOC容器
     */
    public void refresh() {
        // TODO 定位、加载
        // 通过在configLocations中的配置文件进行定位解析，并加载所有的class文件，
        // 使用definitionReader定位查找所有的class文件，进行保存到beanDefinitions中

        /**
         * 1.BeanDefinitionReader实例化的时候
         *  1.1 通过配置文件加载BeanDefinitionReader.SCAN_PACKAGE目录下的所有class文件，
         *  1.2 并将对应的bean的className加载到List中
         * 2.通过loadBeanDefinitions()获得装有加载所有class文件的className的信息
         */

        this.definitionReader = new BeanDefinitionReader(configLocations);
        List<String> beanDefinitions = definitionReader.loadBeanDeifinitions();

        // TODO 注册
        // 将存放的类文件的全限定类名com.guaoran.source.spring.demo.demo.mvc.action.DemoAction的文件进行注册

        /**
         * 1.将BeanDefinitionReader加载的className的list集合进行遍历
         * 2.遍历时进行判断，如果是接口，则不进行注册，否则进行注册
         * 3.注册时调用BeanDefinitionReader的registerBean(className)方法，将className的信息转换成BeanDefinition信息
         * 4.并将转换后的BeanDefinition信息装到IOC容器中，即：beanDefinitionMap中，以该类的首字母小写作为key，封装后的BeanDefinition对象作为value
         * 5.如果是接口的实现，则拿到接口，并将接口的className 即：全限定类名作为key，将该接口的实现封装成BeanDefinition作为value存入。
         *  5.1 如果是多个实现的话，可以通过设置自定义名称进行控制，这里没实现，spring中是这样的，如果没有设置名称，则spring报错，这里是覆盖
         */
        doRegister(beanDefinitions);

        // TODO 依赖注入（lazy_init=false）
        // 在这里自动调用getBean方法

        /**
         * 1.拿到IOC容器中的所有对象，即beanDefinitionMap，进行遍历，并进行注入
         *  1.1 注入时，当bean是isLazyInit=false的时候才进行加载
         *  1.2 进行加载时，存在循环依赖，所以，需要处理（cycleBean()），比如：{@link com.guaoran.source.spring.framework.demo.service.impl.MyServiceImpl}
         * 2.依赖注入最终要的就是getBean()方法，
         *  2.1 通过读取IOC容器中的bean，返回BeanDefinition。并实例化改Bean
         *     2.1.1 实例化bean的时候，会根据scope，如果是单例，且默认是单例的时候，会通过beanCacheMap进性存储单例bean，如果有则返回，没有通过反射生成一个
         *  2.2 实例化BeanDefinition的实例后会通过aop进行增强处理，并带有事件通知，
         *     2.2.1 通过AOP将BeanDefinition的实例进行AOP代理，返回封装代理对象BeanWrapper
         *     2.2.2 然后加载在application中配置的aop配置信息，封装到AopConfig中，
         *     2.2.3 存储代理对象：使用beanWrapperMap来存储所有被代理的bean，进过封装，后面方便我们进行扩展和控制
         *     2.2.4 在存储代理对象之前和之后进行事件的通知
         *  2.3 进行依赖注入（populateBean()）
         *     2.3.1 由于对象之间存在着循环调用关系，因此，在1.2cycleBean()方法中通过递归进行判断，将进行注入的bean的依赖信息进行了实例化，
         *     2.3.2 所以在这里就可以直接给对象的属性进行赋值，比如：将通过@Autowried注释的接口，赋值为实现类
         *
         */
        doAutowrited();
    }

    /**
     * 将存放类的className的信息转换成beanDefinition
     * 并注册到BeanDefinitionMap中，也就是IOC容器中
     *
     * @param beanDefinitions
     */
    private void doRegister(List<String> beanDefinitions) {
        try {
            /**
             * className 有三种情况，
             *  1.自定义名称
             *  2.默认首字母小写
             *  3.通过接口注入
             */
            for (String className : beanDefinitions) {

                Class<?> beanClass = Class.forName(className);
                //接口不进行注册，用他的实现类来进行实例化
                if (beanClass.isInterface()) {
                    continue;
                }
                //将bean的className转换成BeanDefinition
                BeanDefinition beanDefinition = definitionReader.registerBean(className);
                if (beanDefinition != null) {
                    beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
                }
                //如果有接口，则取出接口的实现
                Class<?>[] interfaceClass = beanClass.getInterfaces();
                for (Class clazz : interfaceClass) {
                    this.beanDefinitionMap.put(clazz.getName(), beanDefinition);
                }
                //初始化容器结束
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 依赖注入：自动依赖注入
     */
    private void doAutowrited() {
        //遍历IOC容器
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            //如果是不懒加载，则进行依赖注入
            if (!beanDefinitionEntry.getValue().isLazyInit()) {
                cycleBean(beanName);
            }
        }
    }


    /**
     * 思路是：当进行依赖注入时，如果实例化bean时，如果存在依赖，则继续是实例该依赖的bean。
     *
     * @param beanName
     */
    public void cycleBean(String beanName) {
        try {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            String className = beanDefinition.getBeanClassName();
            Class clazz = Class.forName(className);

            //判断bean下面有没有依赖的bean，如果有先实例化依赖的bean
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                //如果有注入标识,进行实例化
                if (!field.isAnnotationPresent(Autowried.class)) {
                    continue;
                }
                Autowried autowried = field.getAnnotation(Autowried.class);
                cycleBean(field.getType().getName());
            }
            getBean(beanName);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * 依赖注入
     * 读取BeanDefinition的信息，通过反射创建一个实例并返回，
     * spring的做法是 不会将最原始的bean放出去，而是通过BeanWrapper来进行一次包装
     * 装饰器模式：保留原来的oop关系；功能的扩展和增强，为AOP做基础
     * 此处返回AOP代理的类
     * @param beanName
     * @return
     */
    @Override
    public Object getBean(String beanName) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);



        //实例化bean
        Object instance = instantionBean(beanDefinition);
        if (instance == null) {
            return null;
        }

        try {
            //生成通知事件
            BeanPostProcessor beanPostProcessor = new BeanPostProcessor();
            //实例化之前调用一次事件通知
            beanPostProcessor.postProcessBeforeInitialization(instance, beanName);

            //将bean的实例进行包装
            BeanWrapper beanWrapper = new BeanWrapper(instance);

            //将aopConfig信息透传到AopProxy中
            beanWrapper.setAopConfig(instantionAopConfig(beanDefinition));

            beanWrapper.setPostProcessor(beanPostProcessor);
            //将代理的bean放到代理容器中
            beanWrapperMap.put(beanName, beanWrapper);

            //实例化之后调用一次事件通知
            beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            //依赖注入
            populateBean(instance, beanName);
            //通过将代理类进行保存起来后，再从存储所有代理类中取出来，获得代理类
            //通过这样调用，相当于给我们自己留有了可操作的空间
            return this.beanWrapperMap.get(beanName).getWrapperInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 封装AOP配置信息
     * @param beanDefinition
     * @return
     * @throws Exception
     */
    private AopConfig instantionAopConfig(BeanDefinition beanDefinition) throws Exception{
        //读取配置文件中的配置信息
        String expression = definitionReader.getContextConfig().getProperty("pointCut");
        String[] before = definitionReader.getContextConfig().getProperty("aspectBefore").split("\\s");
        String[] after = definitionReader.getContextConfig().getProperty("aspectAfter").split("\\s");
        //获取要增强的类的class信息，进行正则匹配
        String className = beanDefinition.getBeanClassName();
        Class<?> clazz = Class.forName(className);
        //按空格分隔，能拿到增强处理时，进行处理的增强类
        Class aspectClass = Class.forName(before[0]);
        //进行匹配
        Pattern pattern = Pattern.compile(expression);
        Method[] methods = new Method[]{aspectClass.getMethods()[0],aspectClass.getMethods()[1]};
        AopConfig aopConfig = new AopConfig();
        for (Method m :clazz.getMethods()) {
            Matcher matcher = pattern.matcher(m.toString());
            //如果匹配成功则添加到aop配置中
            if(matcher.matches()){
                aopConfig.put(m,aspectClass.newInstance(),methods);
            }
        }
        return aopConfig;
    }

    /**
     * 依赖注入
     *
     * @param instance
     * @param beanName
     */
    private void populateBean(Object instance, String beanName) {
        Class<?> clazz = instance.getClass();
        //如果是controller和service 进行注入
        if (!(clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class))) {
            return;
        }
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //如果有注入标识,进行依赖注入
            if (!field.isAnnotationPresent(Autowried.class)) {
                continue;
            }
            Autowried autowried = field.getAnnotation(Autowried.class);
            //如果自定义名称了，使用自定义的名称，如果没自定义使用默认的名称
            String autowriedBeanName = autowried.value();
            if ("".equals(autowriedBeanName)) {
                autowriedBeanName = field.getType().getName();
            }
            field.setAccessible(true);
            try {
                field.set(instance, beanWrapperMap.get(autowriedBeanName).getWrapperInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }
    }


    /**
     * 实例化bean，保证bean是单例的
     *
     * @param beanDefinition
     * @return
     */
    private Object instantionBean(BeanDefinition beanDefinition) {
        Object instance = null;
        // 查看BbeanCacheMap中是否有该bean的实例，有的话拿出来，没有的话，通过反射生成一个bean
        String className = beanDefinition.getBeanClassName();
        synchronized (beanDefinition) {
            //如果是单例
            if (beanDefinition.getSocpe() == null || "singleton".equals(beanDefinition.getSocpe())) {
                try {
                    //如果已经存在，则重单例容器中取出
                    if (beanCacheMap.containsKey(className)) {
                        instance = beanCacheMap.get(className);
                    } else {
                        //如果未存在，则通过反射重新生成一个实例，并保存到单例容器中
                        Class<?> clazz = Class.forName(className);
                        instance = clazz.newInstance();
                        beanCacheMap.put(className, instance);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        return instance;
    }


    /**
     * 获得所有BeanDefinition的长度
     * @return
     */
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    /**
     * 获得所有BeanDefinition的className
     * @return
     */
    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[beanDefinitionMap.size()]);
    }

    public Properties getConfig(){
        return definitionReader.getContextConfig();
    }
}
